﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using CubeDetection;

namespace CubeCalculations
{
   public class CubeSolutionManager
   {
      public event EventHandler OnInterruptComplete;

      private void InvokeOnInterruptComplete()
      {
         if (OnInterruptComplete == null)
            return;

         foreach (EventHandler Callback in OnInterruptComplete.GetInvocationList())
         {
            var invoke = Callback.Target as ISynchronizeInvoke;
            if (invoke == null)
               continue;
            if (invoke.InvokeRequired)
               invoke.Invoke(Callback, new object[] { this, EventArgs.Empty });
            else
               Callback(this, EventArgs.Empty);
         }
      }

      public event EventHandler OnReady;

      private void InvokeOnReady(object sender, EventArgs e)
      {
         if (OnReady == null)
            return;

         foreach (EventHandler Callback in OnReady.GetInvocationList())
         {
            var invoke = Callback.Target as ISynchronizeInvoke;
            if (invoke == null)
               continue;
            if (invoke.InvokeRequired)
               invoke.Invoke(Callback, new object[] { this, e });
            else
               Callback(this, e);
         }
      }
      public delegate void StatusUpdateHandler(SolveMethod SolveMethod, float Progress, float Fitness, float AbsFitnessImprovement, float RelFitnessImprovement);

      public event StatusUpdateHandler OnStatusChanged;

      private void InvokeOnStatusChanged(SolveMethod SolveMethod, float Progress, float Fitness)
      {
         float relImprovement = BaseSolution.Key - Fitness;
         float absImprovement = (relImprovement / BaseSolution.Key) * 100f;
         
         if (OnStatusChanged == null)
            return;
         foreach (StatusUpdateHandler Callback in OnStatusChanged.GetInvocationList())
         {
            var invoke = Callback.Target as ISynchronizeInvoke;
            if (invoke == null)
               continue;
            if (invoke.InvokeRequired)
               invoke.Invoke(Callback, new object[] { SolveMethod, Progress, Fitness, relImprovement, absImprovement });
            else
               Callback(SolveMethod, Progress, Fitness, relImprovement, absImprovement);
         }
      
      }

      public delegate void SolutionFoundHandler(List<int> Solution, float Fitness, float AbsFitnessImprovement, float RelFitnessImprovement);

      public event SolutionFoundHandler OnSolutionFound;

      private void InvokeOnSolutionFound(List<int> Solution, float Fitness)
      {
         if(OnSolutionFound == null)
            return;

         float relImprovement = BaseSolution.Key - Fitness;
         float absImprovement = (relImprovement / BaseSolution.Key) * 100f;
         
         foreach (SolutionFoundHandler Callback in OnSolutionFound.GetInvocationList())
         {
            var invoke = Callback.Target as ISynchronizeInvoke;
            if(invoke == null)
               continue;
            if (invoke.InvokeRequired)
               invoke.Invoke(Callback, new object[] { Solution, Fitness, absImprovement, relImprovement });
            else
               Callback(Solution, Fitness, absImprovement, relImprovement );
         }
      }

      private readonly ThreadedEvolutionaryCubexSolutionNPlusAlpha EvolutionarySolution;
      private readonly ThreadedOptimalSolver OptimalSolution;
      readonly MovementRating SolutionRating;
      private bool IsActive;
      private SolveMethod CurrentSolveMethod;
      private KeyValuePair<float, List<int>> BaseSolution;
      private readonly Cubex BaseCube;
      private readonly TimeStop Timer;
      private readonly OptimalCubeSolver OptInit;
      public CubeSolutionManager()
      {
         Timer = new TimeStop();
         SolutionRating = new MovementRating();
         BaseCube = new Cubex();
         EvolutionarySolution = new ThreadedEvolutionaryCubexSolutionNPlusAlpha();
         EvolutionarySolution.OnSetSolution += EvolutionarySolution_OnSetSolution;
         EvolutionarySolution.OnStatusChanged += EvolutionarySolution_OnStatusChanged;
         OptimalSolution = new ThreadedOptimalSolver();
         OptimalSolution.OnSetSolution += OptimalSolution_OnSetSolution;
         OptimalSolution.OnStatusChanged += OptimalSolution_OnStatusChanged;

         OptInit = new OptimalCubeSolver();
         OptInit.OnReady += InvokeOnReady;
      }
      public void BeginInit()
      {
         OptInit.BeginInit();
      }

      void OptimalSolution_OnStatusChanged(float Progress, float Fitness)
      {
         InvokeOnStatusChanged(SolveMethod.Distance, Progress, Fitness);
      }

      void EvolutionarySolution_OnStatusChanged(float Progress, float Fitness)
      {
         InvokeOnStatusChanged(SolveMethod.Evolutionary, Progress, Fitness);
      }

      void OptimalSolution_OnSetSolution(List<int> Solution, float Fitness)
      {
         IsActive = false;
         if (!Interrupted)
         {
            InvokeOnSolutionFound(Solution, Fitness);
            Trace.WriteLine("Total time: " + Timer.TimeSpan());
         }
         else
         {
            InvokeOnInterruptComplete();
         }
      }


      void EvolutionarySolution_OnSetSolution(ThreadedEvolutionaryCubexSolutionNPlusAlpha.BacktrackingList Solution)
      {
         if (!Interrupted)
         {
            OptimalSolution.SolveAsync(BaseCube, Solution);
            CurrentSolveMethod = SolveMethod.Distance;
         }
         else
         {
            InvokeOnInterruptComplete();
            IsActive = false;
         }
      }

      private bool Interrupted;
      public void Interrupt()
      {
         Interrupted = true;
         if(CurrentSolveMethod == SolveMethod.Evolutionary)
            EvolutionarySolution.Interrupt();
         else
            OptimalSolution.Interrupt();
      }
      public bool Solve(Cubex Cube)
      {
         lock (this)
         {
            if(!IsActive)
               IsActive = true;
            else
               return false;
            Interrupted = false;
         }
         CurrentSolveMethod = SolveMethod.Evolutionary;
         Cube.CopyTo(BaseCube);
         BaseCube.SolveCube();
         float BaseFitness = SolutionRating.RateSolution(BaseCube);
         List<int> BaseSol = Cube.DelegateSolution();
         BaseSolution = new KeyValuePair<float, List<int>>(BaseFitness, BaseSol);

         EvolutionarySolution.Cube = BaseCube;
         Timer.Reset();
         if (!EvolutionarySolution.EvaluateSolutions())
            return false;
         return true;
      }
   }
}
